package com.yunban.project.message.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yunban.project.message.domain.OpenidInfo;
import com.yunban.project.message.mapper.OpenidInfoMapper;
import com.yunban.project.message.service.WeChatService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
import net.lab1024.sa.admin.module.system.employee.service.EmployeeService;
import net.lab1024.sa.base.common.constant.StringConst;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.base.constant.LoginDeviceEnum;
import org.apache.tomcat.util.http.parser.Authorization;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriUtils;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

/**
 * @author: lijiajun
 * @date: 2025-04-02
 * @version: 1.0
 */
@Slf4j
@Service
public class WeChatServiceImpl implements WeChatService {

    private String appid = "wx1d82df777c51d14c";

    private String callback = "https://java.maxing.cn/sa-api/login/wechat/callback";

    private String inYunbanDomain = "in.yunban.cn";


    public static final String WECHAT_AUTHORIZATION_URL = "https://%s/wx/%s?source=common&appid=%s&redirect_uri=%s&state=original&source=common";

    @Resource
    private EmployeeService employeeService;

    @Resource
    private OpenidInfoMapper openidInfoMapper;

    @SneakyThrows
    @Override
    public void loginByWechat(String redirectCallback, HttpServletResponse response) {
        response.sendRedirect(this.generateWechatRedirectUrl(redirectCallback, callback));
    }

    @Override
    public String generateWechatRedirectUrl(String callback, String url) {
        // url encode callback
        String encodeCallback = UriUtils.encode(callback, StandardCharsets.UTF_8);
        // 判断 url 是否包含 ？
        String glue = url.contains("?") ? "&" : "?";
        // 生成回调地址
        String redirectURI = url + glue + "callback=" + encodeCallback + "&scope=snsapi_base";
        return String.format(WECHAT_AUTHORIZATION_URL, this.inYunbanDomain, "oauth", this.appid, UriUtils.encode(redirectURI, StandardCharsets.UTF_8));
    }

    @SneakyThrows
    @Override
    public void weChatCallback(HttpServletRequest request, HttpServletResponse response) {
        String openid = request.getParameter("openid");
        String appid = request.getParameter("appid");
        String time = request.getParameter("time");
        String callback = request.getParameter("callback");
        String source = request.getParameter("source");
        log.info("微信回调参数：openid={},appid={},time={},callback={},source={}", openid, appid, time, callback, source);
        if (!StringUtils.hasText(openid)) {
            throw new BusinessException("无效的openid，请确认参数是否正确");
        }
        // 应替换为实际的appid
        if (!this.appid.equals(appid)) {
            throw new BusinessException("无效的openid，请确认参数是否正确");
        }
        if (!StringUtils.hasText(request.getParameter("callback"))) {
            throw new BusinessException("回调地址错误，请确认参数是否正确");
        }
        log.info("currentTime={},time={}", System.currentTimeMillis(), Long.parseLong(time) + 30);
        long oauthTime = (Long.parseLong(time) + 30) * 1000;
        if (oauthTime < System.currentTimeMillis()) {
            throw new BusinessException("OAuth timeout, retry please.");
        }
        // 校验签名
        Map<String, String> map = this.validSignature(request);
        log.info("map:{}", map);
        OpenidInfo openidInfo = this.openidInfoMapper.selectById(map.get("openid"));
        if (ObjectUtils.isEmpty(openidInfo)) {
            openidInfo = new OpenidInfo();
            openidInfo.setOpenid(map.get("openid"));
            this.openidInfoMapper.insert(openidInfo);
        }
        // 通过 openid 查询用户信息，看看是否有权进入系统（相当于白名单）
        EmployeeEntity employeeEntity = this.employeeService.findByOpenid(map.get("openid"));
        if (null == employeeEntity) {
            // 没有问号，直接使用?添加参数
            response.sendRedirect("https://yq.maxing.cn/public-opinion/oauth-refuse.html");
            return;
        }
        String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
        // 登录
        StpUtil.login(saTokenLoginId, String.valueOf(LoginDeviceEnum.H5.getDesc()));
        // 获取登录结果信息
        String token = StpUtil.getTokenValue();
        String tokenParam = "token=" + token;
        if (callback.contains("?")) {
            // 已经有问号，使用&添加新的参数
            response.sendRedirect(callback + "&" + tokenParam);
        } else {
            // 没有问号，直接使用?添加参数
            response.sendRedirect(callback + "?" + tokenParam);
        }
    }

    @Override
    public Map<String, String> validSignature(HttpServletRequest request) {
        String sign = request.getParameter("sign");
        log.info("sign:{}", sign);
        if (!StringUtils.hasText(sign)) {
            throw new BusinessException("无效的sign，请确认参数是否正确");
        }
        String scope = request.getParameter("scope");
        Map<String, String> generateSignatureParams = switch (scope) {
            case "snsapi_base" -> this.getGenerateSignatureParams(request, true);
            case "snsapi_userinfo" -> this.getGenerateSignatureParams(request, false);
            default -> throw new BusinessException("授权类型错误");
        };
        if (null == generateSignatureParams) {
            throw new BusinessException("签名参数错误");
        }
        String mySign = this.generateSignature(generateSignatureParams, "common_appsecret");
        log.info("mySign:{}", mySign);
        log.info("generateSignatureParams:{}", generateSignatureParams);
        if (!sign.equals(mySign)) {
            throw new BusinessException("SIGNATURE_ERROR");
        }
        return generateSignatureParams;
    }

    String generateSignature(Map<String, String> map, String inYunBanAppSecret) {
        TreeMap<String, String> sortedMap = new TreeMap<>(map);
        StringBuilder sb = new StringBuilder();

        for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
            String value = entry.getValue();
            if (StringUtils.hasText(value)) {
                if ("nickname".equals(entry.getKey()) || "headimg".equals(entry.getKey())) {
                    log.info("nickname or nickname value: {}", value);
                    log.info("nickname or headimg value: {}", value);
                    value = UriUtils.encode(value, StandardCharsets.UTF_8);
                }
                sb.append(entry.getKey()).append("=").append(value).append("&");
            }
        }
        // 移除最后一个多余的"&"
        if (!sb.isEmpty()) {
            sb.setLength(sb.length() - 1);
        }
        // 添加应用密钥并生成签名
        String raw = sb.toString() + inYunBanAppSecret;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(raw.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                hexString.append(String.format("%02X", 0xFF & b));
            }
            return hexString.toString().toUpperCase();
        } catch (NoSuchAlgorithmException e) {
            log.error("MD5 algorithm not found", e);
            // 处理异常，例如记录日志或抛出自定义异常
            throw new BusinessException("MD5 algorithm not found");
        }
    }

    public Map<String, String> getGenerateSignatureParams(HttpServletRequest request, boolean isBase) {
        HashMap<String, String> map = new HashMap<>(8);
        map.put("openid", request.getParameter("openid"));
        map.put("appid", request.getParameter("appid"));
        map.put("source", request.getParameter("source"));
        map.put("time", request.getParameter("time"));
        if (!isBase) {
            map.put("nickname", request.getParameter("nickname"));
            map.put("headimg", request.getParameter("headimg"));
        }
        return map;
    }
}
